using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Data.Linq.Mapping;
using System.Web;
using FiltronASP.Models.DAO.Interfaces;
using FiltronASP.Logger;
using System.Collections.ObjectModel;
using System.Reflection;
using System.Data.Common;

namespace FiltronASP.Models.DAO.Abstracts
{
    /// <summary>
    /// Klasa abstrakcyjna implementujaca interfejs <code>IGenericDAO</code> 
    /// i zawierajaca podstawowe metody do zarzadzania obiektami z bazy danych.
    /// </summary>
    /// <typeparam name="T">obiekt z bazy</typeparam>
    /// <typeparam name="PK">klucz glowny</typeparam>
    public abstract class AbstractGenericDAO<T, PK> : IGenericDAO<T, PK> where T : class
    {

        protected CMSEntitiesDataContext _cmsEntitiesDataContext;

        protected static Dictionary<Type, MetaDataMember> KEY_CACHE = new Dictionary<Type, MetaDataMember>();

        /// <summary>
        /// Metoda zwracajaca nazwe parametru bedacego kluczem glownym tabeli.
        /// </summary>
        /// <returns>nazwa klucza glownego</returns>
        protected abstract string GetKeyProperty();

        /// <summary>
        /// Domyslny konstruktor.
        /// </summary>
        public AbstractGenericDAO()
        {
            this._cmsEntitiesDataContext = new CMSEntitiesDataContext();
        }

        /// <summary>
        /// Konstruktor.
        /// </summary>
        /// <param name="cmsEntitiesDataContext">mapowania tabel</param>
        public AbstractGenericDAO(CMSEntitiesDataContext cmsEntitiesDataContext)
        {
            this._cmsEntitiesDataContext = cmsEntitiesDataContext;
        }

        /// <summary>
        /// Commit wszystkich zmian.
        /// </summary>
        /// <returns>true, gdy sie uda, false dla porazki</returns>
        public bool SubmitChanges()
        {
            try
            {
                this._cmsEntitiesDataContext.SubmitChanges();
                return true;
            }   //try
            catch (Exception ex)
            {
                MyLogger.Log(ex);
            }   //catch
            return false;
        }

        /// <summary>
        /// Pobieranie obiektu po kluczu glownym.
        /// </summary>
        /// <param name="id">klucz glowny</param>
        /// <returns>rekord o zadanym kluczu glownym lub null, gdy nie ma</returns>
        public T FindByPK(PK id)
        {
            if (id == null)
            {
                return null;
            }   //if
            try
            {
                return this._cmsEntitiesDataContext.GetTable<T>().SingleOrDefault(this.GetKeyLambda(id));
            }   //try
            catch (Exception ex)
            {
                MyLogger.Log(ex);
            }   //catch
            return null;
        }

        /// <summary>
        /// Pobieranie wszystkich obiektow.
        /// </summary>
        /// <returns>liste wszystkich rekordow z tabeli</returns>
        public IQueryable<T> FindAll()
        {
            try 
	        {
                return this._cmsEntitiesDataContext.GetTable<T>();
	        }   //try
	        catch (Exception ex)
	        {
                MyLogger.Log(ex);
                return null;
            }   //catch
        }

        /// <summary>
        /// Usuwanie wskazanego obiektu.
        /// </summary>
        /// <param name="persistentObject">obiekt do usuniecia</param>
        /// <returns>true dla sukcesu, false w przeciwnym razie</returns>
        public bool Delete(PK id)
        {
            if (id == null)
            {
                return true;
            }   //if
            try
            {
                T entityToRemove = this._cmsEntitiesDataContext.GetTable<T>().Single(this.GetKeyLambda(id));
                this._cmsEntitiesDataContext.GetTable<T>().DeleteOnSubmit(entityToRemove);
                return true;
	        }   //try
	        catch (Exception ex)
	        {
                MyLogger.Log(ex);
            }   //catch
            return false;
        }

        /// <summary>
        /// Dodawanie wskazanego obiektu.
        /// </summary>
        /// <param name="persistentObject">obiekt do dodania</param>
        /// <returns>dodany obiekt lub null dla porazki</returns>
        public T Add(T persistentObject)
        {
            if (persistentObject == null)
            {
                return null;
            }   //if
            try
            {
                CMSEntitiesDataContext cmsEntitiesDataContext = this._cmsEntitiesDataContext;
                MetaDataMember keyMetaDataMember = null;
                if (!KEY_CACHE.ContainsKey(typeof(T)))
                {
                    ReadOnlyCollection<MetaDataMember> metaDataMembers =
                        cmsEntitiesDataContext.Mapping.MappingSource.
                        GetModel(cmsEntitiesDataContext.GetType()).
                        GetMetaType(typeof(T)).DataMembers;
                    int keyCount = 0;
                    foreach (MetaDataMember metaDataMember in metaDataMembers)
                    {
                        if (metaDataMember.IsPrimaryKey)
                        {
                            keyMetaDataMember = metaDataMember;
                            keyCount++;
                        }   //if pk
                    }   //foreach
                    if (keyCount == 0)
                    {
                        throw new Exception(String.Format("Could not find a primary key for entity {0}. You must override the Save method to persist this entity.", persistentObject));
                    }   //if no pk
                    if (keyCount > 1)
                    {
                        throw new Exception(String.Format("AbstractGenericDAO does not support composite keys (Your entity {0} has more than one primary key). You must override the save method in your subclass.", persistentObject));
                    }   //if composite pk
                    KEY_CACHE[typeof(T)] = keyMetaDataMember;
                }   //if not in cache
                else
                {
                    keyMetaDataMember = KEY_CACHE[typeof(T)];
                }   //else not in cache
                this._cmsEntitiesDataContext.GetTable<T>().InsertOnSubmit(persistentObject);
                this._cmsEntitiesDataContext.SubmitChanges();
                return this.FindByPK((PK)persistentObject.GetType().InvokeMember(keyMetaDataMember.Member.Name,
                    BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty, null, persistentObject, null));
            }   //try
            catch (Exception ex)
            {
                MyLogger.Log(ex);
            }   //catch
            return null;
        }

        /// <summary>
        /// Metoda z IDisposable, automatyczny commit na koniec.
        /// </summary>
        public void Dispose()
        {
            try
            {
                this._cmsEntitiesDataContext.SubmitChanges();
            }   //try
            catch (Exception ex)
            {
                MyLogger.Log(ex);
            }   //catch
        }

        /// <summary>
        /// Metoda generycznego pobrania wyrazenia odpowiadajego wyborowi klucza glownego.
        /// </summary>
        /// <param name="id">klucz glowny</param>
        /// <returns>wyrazenie z kluczem glownym</returns>
        public Expression<Func<T, bool>> GetKeyLambda(PK id)
        {
            var parameter = Expression.Parameter(typeof(T), "t");
            var propId = Expression.Property(parameter, typeof(T).GetProperty(GetKeyProperty()));
            return Expression.Lambda<Func<T, bool>>(Expression.Equal(propId, Expression.Constant(id)), parameter);        
        }

    }
}
